using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Windows.Markup;
using Rhizome.Contract;
using Rhizome.Contract.Xaml;
using Rhizome.Internal;

namespace Rhizome.Model
{
	/// <summary>
	/// Converts string to Actions. 
	/// Emulates markup extension syntax.
	/// Silverlight does not support custom markup extension :(
	/// </summary>
	public sealed class ActionTypeConverter : TypeConverter
	{
		#region Overrides of TypeConverter

		public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
		{
			return sourceType == typeof(string);
		}

		public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
		{
			var actions = CreateActions((string) value).ToArray();
			switch (actions.Length)
			{
				case 0: return null;
				case 1: return actions[0];
				default: return new MultiAction(actions);
			}
		}

		#endregion

		public static IEnumerable<IAction> CreateActions(string str)
		{
			return 
				from s in str.Split(';')
				let a = s.Trim()
				where a.IsNullOrEmpty() == false
				select CreateAction(a);
		}

		private static IAction CreateAction(string s)
		{
			s = s.Trim();

			// checks for setter syntax
			// Property = Value
			var args = s.Split(new[] { '=' }, StringSplitOptions.RemoveEmptyEntries);
			if (args.Length == 2 && args[0].Trim().Contains(" ") == false)
			{
				var setter = new Setter();
				setter.Init(s);
				return setter;
			}

			// checks for MarkupExtension like syntax
			// Type ContentPropertyValue, Property = Value...
			var words = s.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
			var typeName = words.FirstOrDefault();
			if (typeName == null) throw new InvalidOperationException("Type name not set");
			if (typeName.Contains(".") == false) typeName = "Rhizome.Model.{0}".With(typeName);

			// todo: add external assembly support ?
			var action = (IAction) Assembly.GetExecutingAssembly().CreateInstance(typeName);
			if (action == null) throw new InvalidOperationException("Type {0} not found".With(typeName));

			var type = action.GetType();

			var initStr = s.Remove(0, words[0].Length).TrimStart();

			var init = action as IInitFromString;
			if (init != null)
			{
				init.Init(initStr);
				return action;
			}

			args = initStr.Replace(" ", string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

			if (args.Length > 0)
			{
				foreach (var arg in args)
				{
					string propName;
					object propVal;
					if (arg.Contains("="))
					{
						var pair = arg.Split('=');
						if (pair.Length != 2) throw new InvalidOperationException("Invalid action syntax"); // todo: clarify message
						propName = pair.First();
						propVal = pair[1];
					}
					else
					{
						var attr = (ContentPropertyAttribute)Attribute.GetCustomAttribute(type, typeof(ContentPropertyAttribute), true); // todo: cache
						if (attr == null) throw new InvalidOperationException("{0}: No ContentPropertyAttribute".With(typeName));
						if (attr.Name.IsNullOrEmpty())
							throw new InvalidOperationException("{0}: ContentPropertyAttribute.Name not set".With(typeName));

						propName = attr.Name;
						propVal = arg;
					}

					var propertyInfo = type.GetProperty(propName);
					if (propertyInfo == null)
						throw new InvalidOperationException("Property {0}.{1} not found".With(typeName, propName));

					var val = propVal.ChangeType(propertyInfo.PropertyType);
					propertyInfo.SetValue(action, val, null);
				}

			}

			return action;
		}
	}
}